package com.hmall.search.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.dto.ItemDTO;
import com.hmall.search.domain.dto.AggregationDTO;
import com.hmall.search.domain.dto.ESPageDTO;
import com.hmall.search.domain.dto.ItemDoc;
import com.hmall.search.domain.query.ItemPageQuery;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.hmall.common.constants.ESConstants.ITEM_INDEX;

@Slf4j
@Service
@RequiredArgsConstructor
public class SearchService {

    private final RestHighLevelClient client;

    public ESPageDTO<ItemDTO> search(ItemPageQuery query) throws IOException {
        // 1.创建request对象
        SearchRequest request = new SearchRequest(ITEM_INDEX);

        // 2.配置request对象
        SearchSourceBuilder searchSourceBuilder = buildSearchSourceBuilder(query);

        request.source(searchSourceBuilder);

        // 3.发送请求
        SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
        // System.out.println("resp = " + resp);


        ESPageDTO<ItemDTO> pageDTO = parseResponseResult(resp, query);

        return pageDTO;
    }

    public AggregationDTO searchFilters(ItemPageQuery query) throws IOException {
        // 1.创建request对象
        SearchRequest request = new SearchRequest(ITEM_INDEX);

        // 2.配置request对象
        SearchSourceBuilder searchSourceBuilder = buildSearchSourceBuilder(query);
        searchSourceBuilder
                .size(0)
                .aggregation(
                        AggregationBuilders.terms("brand_agg")
                                .field("brand")
                                .size(10)
                ).aggregation(
                        AggregationBuilders.terms("category_agg")
                                .field("category")
                                .size(10)
                );
        request.source(searchSourceBuilder);
        // 3.发送请求
        SearchResponse resp = client.search(request, RequestOptions.DEFAULT);
        // 4.处理结果

        List<String> brandList = new ArrayList<>();
        List<String> categoryList = new ArrayList<>();

        Aggregations aggregations = resp.getAggregations();
        Terms brandAgg = aggregations.get("brand_agg");
        brandAgg.getBuckets().forEach(bucket -> {
            // 4.1.获取品牌名称
            String brandName = bucket.getKeyAsString();
            // 4.2.获取数量
            long count = bucket.getDocCount();
            brandList.add(brandName);
            log.info("品牌名称：{}，数量：{}", brandName, count);
        });
        Terms categoryAgg = aggregations.get("category_agg");
        categoryAgg.getBuckets().forEach(bucket -> {
            // 4.1.获取品牌名称
            String categoryName = bucket.getKeyAsString();
            // 4.2.获取数量
            long count = bucket.getDocCount();
            categoryList.add(categoryName);
            log.info("分类名称：{}，数量：{}", categoryName, count);
        });
        AggregationDTO aggregationDTO = new AggregationDTO();
        aggregationDTO.setBrand(brandList);
        aggregationDTO.setCategory(categoryList);

        return aggregationDTO;
    }

    /**
     * 构建查询条件
     *
     * @param query
     * @return
     */
    private SearchSourceBuilder buildSearchSourceBuilder(ItemPageQuery query) {
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.from(query.from())
                .size(query.getPageSize());

        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        if (StrUtil.isNotBlank(query.getKey())) {
            queryBuilder.must(QueryBuilders.matchQuery("name", query.getKey()));
        }
        if (StrUtil.isNotBlank(query.getBrand())) {
            queryBuilder.filter(QueryBuilders.termQuery("brand", query.getBrand()));
        }
        if (StrUtil.isNotBlank(query.getCategory())) {
            queryBuilder.filter(QueryBuilders.termQuery("category", query.getCategory()));
        }
        if (query.getMinPrice() != null) {
            queryBuilder.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()));
        }
        if (query.getMaxPrice() != null) {
            queryBuilder.filter(QueryBuilders.rangeQuery("price").lte(query.getMaxPrice()));
        }

        // sourceBuilder.query(queryBuilder);
        //精准总数
        sourceBuilder.trackTotalHits(true);
        // 添加排序逻辑
        if (StrUtil.isNotEmpty(query.getSortBy())) {
            SortOrder order = query.getIsAsc() ? SortOrder.ASC : SortOrder.DESC;
            sourceBuilder.sort(query.getSortBy(), order);
        }
        // 高亮字段
        sourceBuilder.highlighter(
                SearchSourceBuilder.highlight()
                        .field("name")
        );
        // 使用 functionScoreQuery 添加对 isAD 字段的排序
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(queryBuilder,
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        QueryBuilders.termQuery("isAD", true),
                                        ScoreFunctionBuilders.weightFactorFunction(100)
                                )
                        })
                .boostMode(CombineFunction.MULTIPLY); // 乘法模式，将原始分数与 function score 相乘

        sourceBuilder.query(functionScoreQueryBuilder);


        return sourceBuilder;
    }


    /**
     * 解析搜索响应结果
     *
     * @param resp  搜索响应对象
     * @param query
     * @return 解析后的文档列表
     */
    private ESPageDTO<ItemDTO> parseResponseResult(SearchResponse resp, ItemPageQuery query) {
        // 4.处理结果
        SearchHits searchHits = resp.getHits();
        // 4.1.获取总记录数
        long total = searchHits.getTotalHits().value;
        log.info("总记录数：{}", total);

        // 4.2.获取所有记录
        SearchHit[] hits = searchHits.getHits();
        List<ItemDoc> list = new ArrayList<>();
        for (SearchHit hit : hits) {
            // 4.2.1.获取文档内容 得到 source
            String json = hit.getSourceAsString();
            // log.info("json = {}", json);
            // 4.2.2.反序列化成对象
            ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);

            // 5.获取高亮字段
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (CollUtil.isNotEmpty(highlightFields)) {
                // 5.1.获取高亮字段
                HighlightField hf = highlightFields.get("name");
                // 5.2.（简化版）获取第一个高亮片段，就是 name 的高亮值
                /**
                 * 完整版：ES底层处理高亮字段时会有一个长度阈值，单个字段可能无法存放完整的高亮内容，
                 * 因此高亮内容会被拆分成多个片段存储在数组中。
                 * 需要通过遍历 hf.getFragments() 方法将每个高亮片段拼接起来，以还原完整的高亮内容。
                 *
                 * 简化版逻辑仅取了第一个高亮片段，适用于高亮内容较短且无需完整展示的场景。
                 * 如果需要完整高亮内容，请参考以下代码：
                 *
                 * StringBuilder completeHighlight = new StringBuilder();
                 * for (Text fragment : hf.getFragments()) {
                 *     completeHighlight.append(fragment.toString());
                 * }
                 * String completeName = completeHighlight.toString();
                 * log.info("完整高亮名称 = {}", completeName);
                 */

                if (hf != null) {
                    StringBuilder completeHighlight = new StringBuilder();
                    for (Text fragment : hf.getFragments()) {
                        completeHighlight.append(fragment.toString());
                    }
                    String completeName = completeHighlight.toString();

                    // log.info("完整高亮名称 = {}", completeName);
                    itemDoc.setName(completeName);
                }

            }

            list.add(itemDoc);
            // log.info("itemDoc = {}", itemDoc);
        }

        long pages = total / query.getPageSize();
        if (total % query.getPageSize() != 0L) {
            ++pages;
        }
        List<ItemDTO> itemDTOS = BeanUtil.copyToList(list, ItemDTO.class);
        ESPageDTO<ItemDTO> pageDTO = new ESPageDTO<>(total, pages, itemDTOS);
        return pageDTO;
    }
}